﻿using System;
using System.Text;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;

namespace Campus
{
    #region Type Definitions

    /// <summary>
    /// The state of scrolling for the cursor
    /// </summary>
    public enum CursorScrollState { scrollNONE  = 0x00, 
                                    scrollL     = 0x01, 
                                    scrollR     = 0x02, 
                                    scrollU     = 0x04, 
                                    scrollD     = 0x08, 
                                    scrollUL    = 0x05, 
                                    scrollUR    = 0x06, 
                                    scrollDL    = 0x09, 
                                    scrollDR    = 0x0a
                                  };

    /// <summary>
    /// The state a cursor can be in besides scrolling
    /// </summary>
    public enum CursorActionState { idle, click, attack, move, invisible };

    #endregion

    public class Cursor
    {
        #region Fields and Properties

        public static int SCROLL_BUFFER_SIZE = 75;
        public static int SCROLL_DELAY = 200;

        // These represent the constants used to know when to stop scrolling
        // The further the limit, the more you can go into 'nowhere'
        public static int SCROLL_LIMIT_YMIN = -200;
        public static int SCROLL_LIMIT_YMAX = 900;
        public static int SCROLL_LIMIT_XMIN = -100;
        public static int SCROLL_LIMIT_XMAX = 1200;

        private Vector2 m_position;
        private MouseState m_oldMouseState;
        private int m_timeInScrollArea = 0;

        private Animation m_currentAnimation;

        /// <summary>
        /// The list of animations mapped to action states of the mouse.
        /// </summary>
        public Dictionary<CursorActionState, Animation> ActionAnimations { get; private set; }

        /// <summary>
        /// The origins for the animation of each action state.
        /// </summary>
        public Dictionary<CursorActionState, Vector2> ActionOrigins { get; set; }

        /// <summary>
        /// The list of animations mapped to scrolling states of the mouse.
        /// </summary>
        public Dictionary<CursorScrollState, Animation> ScrollAnimations { get; private set; }

        /// <summary>
        /// The origins for the animation of each action state.
        /// </summary>
        public Dictionary<CursorScrollState, Vector2> ScrollOrigins { get; set; }

        /// <summary>
        /// The position of the 'Cursor' in screen coordinates
        /// </summary>
        public Vector2 Position 
        {
            get { return m_position; }
            set { m_position = value; } 
        }

        /// <summary>
        /// The scroll state of the mouse.  This cannot be set outside the Cursor class.
        /// </summary>
        public CursorScrollState ScrollState { get; private set; }

        /// <summary>
        /// The current action state of the mouse.
        /// </summary>
        public CursorActionState ActionState { get; set; }

        /// <summary>
        /// The scroll speed in pixels per second
        /// </summary>
        public float ScrollSpeed { get; set; }

        public delegate void OnNextMouseClick(Vector2 gamePoint);
        public OnNextMouseClick ClickMethod { get; set; }

        #endregion

        #region Constructors

        public Cursor(Dictionary<CursorActionState, Animation> actionAnimations, 
                      Dictionary<CursorScrollState, Animation> scrollAnimations)
        {
            this.ActionAnimations = actionAnimations;
            this.ScrollAnimations = scrollAnimations;

            this.ActionState = CursorActionState.idle;
            this.ScrollState = CursorScrollState.scrollNONE;

            this.Position = new Vector2(0.0f, 0.0f);
            this.ScrollSpeed = 300.0f;

            this.ScrollOrigins = new Dictionary<CursorScrollState, Vector2>()
            {
                { CursorScrollState.scrollUL, Vector2.Zero },
                { CursorScrollState.scrollU, Vector2.Zero },
                { CursorScrollState.scrollUR, Vector2.Zero },
                { CursorScrollState.scrollL, Vector2.Zero },
                { CursorScrollState.scrollR, Vector2.Zero },
                { CursorScrollState.scrollDL, Vector2.Zero },
                { CursorScrollState.scrollD, Vector2.Zero },
                { CursorScrollState.scrollDR, Vector2.Zero }
            };

            this.ActionOrigins = new Dictionary<CursorActionState, Vector2>()
            {
                { CursorActionState.attack, Vector2.Zero },
                { CursorActionState.click, Vector2.Zero },
                { CursorActionState.idle, Vector2.Zero },
                { CursorActionState.move, Vector2.Zero }
            };

            this.m_currentAnimation = actionAnimations[CursorActionState.idle];
            this.m_oldMouseState = Mouse.GetState();
        }

        #endregion

        #region Methods

            #region Public

            public static BaseEntity GetBaseEntityAtPoint(Vector2 point, Player player) {
                foreach (BaseEntity b in player.CurrentLevel.BaseEntities) {
                    if (point.X >= b.Position.X - b.Origin.X &&
                        point.X <= b.Position.X - b.Origin.X + b.Size.X &&
                        point.Y >= b.Position.Y - b.Origin.Y &&
                        point.Y <= b.Position.Y - b.Origin.Y + b.Size.Y) {
                        return b;
                    }
                }
                return null;
            }

            public static Vector2 GetTileAtPoint(Vector2 point, Player player) {
                Vector2 tile = new Vector2();
                player.CurrentLevel.GetGridPositionForPoint(point, out tile);
                return tile;
            }

            public void Update(GameTime gameTime, Player player, Level currentLevel, 
                               Rectangle gameBoardViewingArea, bool isPaused)
            {
                MouseState mouseState = Mouse.GetState();

                this.m_position.X = mouseState.X;
                this.m_position.Y = mouseState.Y;

                if (ActionState != CursorActionState.attack &&
                    ActionState != CursorActionState.move)
                {
                    if (mouseState.LeftButton == ButtonState.Pressed)
                    {
                        ActionState = CursorActionState.click;
                    }
                    else
                    {
                        ActionState = CursorActionState.idle;
                    }
                }

                // ------------- SCROLLING LOGIC -------------//

                float min, max;
                bool isInScrollingArea = false;

                min = gameBoardViewingArea.Top;
                max = gameBoardViewingArea.Top + SCROLL_BUFFER_SIZE;

                // Don't allow scrolling if the game is paused
                if (!isPaused)
                {
                    // If the mouse button has been clicked
                    if (mouseState.LeftButton == ButtonState.Pressed &&
                        m_oldMouseState.LeftButton == ButtonState.Released)
                    {
                        Vector2 adjustedMousePosition = m_position + player.ScreenPosition;

                        if (ClickMethod != null) 
                        {
                            ClickMethod(adjustedMousePosition);
                        }

                        // Since the delegates handle the action, automatically set the action state
                        // to idle for the mouse so it draws correctly
                        this.ActionState = CursorActionState.idle;
                    }

                    ScrollState = CursorScrollState.scrollNONE;

                    // Try to scroll up
                    if (m_position.Y <= max && m_position.Y >= min)
                    {
                        isInScrollingArea = true;

                        m_timeInScrollArea += gameTime.ElapsedGameTime.Milliseconds;

                        if (m_timeInScrollArea >= SCROLL_DELAY && player.ScreenPosition.Y >= SCROLL_LIMIT_YMIN)
                        {
                            ScrollUp(gameTime, player);
                            ScrollState = ScrollState | CursorScrollState.scrollU;
                        }
                    }
                    else if (m_position.Y < min)
                    {
                        // The mouse is in the HUD, don't reset the time spent in the scroll area
                        return;
                    }

                    min = gameBoardViewingArea.Bottom - SCROLL_BUFFER_SIZE;
                    max = gameBoardViewingArea.Bottom;

                    // Try to scroll down
                    if (m_position.Y <= max && m_position.Y >= min)
                    {
                        if (!isInScrollingArea)
                        {
                            isInScrollingArea = true;
                            m_timeInScrollArea += gameTime.ElapsedGameTime.Milliseconds;
                        }

                        if (m_timeInScrollArea >= SCROLL_DELAY && player.ScreenPosition.Y <= SCROLL_LIMIT_YMAX)
                        {
                            ScrollDown(gameTime, player);
                            ScrollState = ScrollState | CursorScrollState.scrollD;
                        }
                    }
                    else if (m_position.Y > max)
                    {
                        // The mouse is in the HUD, don't reset the time spent in the scroll area
                        return;
                    }

                    min = 0.0f;
                    max = SCROLL_BUFFER_SIZE;

                    // Try to scroll left
                    if (m_position.X <= max && m_position.X >= min)
                    {
                        if (!isInScrollingArea)
                        {
                            isInScrollingArea = true;
                            m_timeInScrollArea += gameTime.ElapsedGameTime.Milliseconds;
                        }

                        if (m_timeInScrollArea >= SCROLL_DELAY && player.ScreenPosition.X >= SCROLL_LIMIT_XMIN)
                        {
                            ScrollLeft(gameTime, player);
                            ScrollState = ScrollState | CursorScrollState.scrollL;
                        }
                    }
                    else if (m_position.X < min)
                    {
                        // The mouse is off the screen, don't reset the time spent in the scroll area
                        return;
                    }

                    min = gameBoardViewingArea.Right - SCROLL_BUFFER_SIZE;
                    max = gameBoardViewingArea.Right;

                    // Try to scroll right
                    if (m_position.X <= max && m_position.X >= min)
                    {
                        if (!isInScrollingArea)
                        {
                            isInScrollingArea = true;
                            m_timeInScrollArea += gameTime.ElapsedGameTime.Milliseconds;
                        }

                        if (m_timeInScrollArea >= SCROLL_DELAY && player.ScreenPosition.X <= SCROLL_LIMIT_XMAX)
                        {
                            ScrollRight(gameTime, player);
                            ScrollState = ScrollState | CursorScrollState.scrollR;
                        }
                    }
                    else if (m_position.X > max)
                    {
                        // The mouse is off the screen, don't reset the time spent in the scroll area
                        return;
                    }

                    // Reset the time the cursor has been in the scrolling area if it wasn't
                    // in it on the last update
                    if (!isInScrollingArea)
                    {
                        m_timeInScrollArea = 0;
                    }
                }
                else
                {
                    // Set the scroll state to NONE since the game is paused
                    ScrollState = CursorScrollState.scrollNONE;
                }

                Animation currentAnimation;

                // Here we update the animation state
                if (ScrollState == CursorScrollState.scrollNONE)
                {
                    currentAnimation = this.ActionAnimations[ActionState];
                }
                else
                {
                    currentAnimation = this.ScrollAnimations[ScrollState];
                }

                if (currentAnimation != m_currentAnimation)
                {
                    m_currentAnimation.Pause();
                    m_currentAnimation.ResetAnimation();
                    m_currentAnimation = currentAnimation;
                }

                if (!currentAnimation.IsAnimating)
                {
                    currentAnimation.Start();
                }

                currentAnimation.Update(gameTime);

                m_oldMouseState = mouseState;
            }

            public void Draw(SpriteBatch spriteBatch)
            {
                if (ActionState != CursorActionState.invisible)
                {
                    spriteBatch.Begin();

                    if (ScrollState == CursorScrollState.scrollNONE)
                    {
                        Texture2D texture = this.ActionAnimations[ActionState].CurrentTexture;
                        Vector2 origin = this.ActionOrigins[ActionState];

                        spriteBatch.Draw(texture, (this.Position - origin), Color.White);
                    }
                    else
                    {
                        Texture2D texture = this.ScrollAnimations[ScrollState].CurrentTexture;
                        Vector2 origin = this.ScrollOrigins[ScrollState];

                        spriteBatch.Draw(texture, (this.Position - origin), Color.White);
                    }

                    spriteBatch.End();
                }
            }

            #endregion

            #region Private

            private void ScrollUp(GameTime gameTime, Player player)
            {
                player.ScreenPosition.Y -= (float)gameTime.ElapsedGameTime.TotalSeconds * this.ScrollSpeed;
            }

            private void ScrollDown(GameTime gameTime, Player player)
            {
                player.ScreenPosition.Y += (float)gameTime.ElapsedGameTime.TotalSeconds * this.ScrollSpeed;
            }

            private void ScrollLeft(GameTime gameTime, Player player)
            {
                player.ScreenPosition.X -= (float)gameTime.ElapsedGameTime.TotalSeconds * this.ScrollSpeed;
            }

            private void ScrollRight(GameTime gameTime, Player player)
            {
                player.ScreenPosition.X += (float)gameTime.ElapsedGameTime.TotalSeconds * this.ScrollSpeed;
            }

            #endregion

        #endregion
    }
}
